package core;

import java.io.File;
import java.net.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * URL（Uniform Resource Locator）统一资源定位符相关工具类
 * <p>
 * 统一资源定位符，描述了一台特定服务器上某资源的特定位置。
 * </p>
 * URL组成：
 * <pre>
 *   协议://主机名[:端口]/ 路径/[:参数] [?查询]#Fragment
 *   protocol :// hostname[:port] / path / [:parameters][?query]#fragment
 * </pre>
 */
public class URLUtil {

    /**
     * 针对ClassPath路径的伪协议前缀（兼容Spring）: "classpath:"
     */
    public static final String CLASSPATH_URL_PREFIX = "classpath:";
    /**
     * URL 前缀表示文件: "file:"
     */
    public static final String FILE_URL_PREFIX = "file:";
    /**
     * URL 前缀表示jar: "jar:"
     */
    public static final String JAR_URL_PREFIX = "jar:";
    /**
     * URL 前缀表示war: "war:"
     */
    public static final String WAR_URL_PREFIX = "war:";
    /**
     * URL 协议表示文件: "file"
     */
    public static final String URL_PROTOCOL_FILE = "file";
    /**
     * URL 协议表示Jar文件: "jar"
     */
    public static final String URL_PROTOCOL_JAR = "jar";
    /**
     * URL 协议表示zip文件: "zip"
     */
    public static final String URL_PROTOCOL_ZIP = "zip";
    /**
     * URL 协议表示WebSphere文件: "wsjar"
     */
    public static final String URL_PROTOCOL_WSJAR = "wsjar";
    /**
     * URL 协议表示JBoss jar文件: "vfszip"
     */
    public static final String URL_PROTOCOL_VFSZIP = "vfszip";
    /**
     * URL 协议表示JBoss文件: "vfsfile"
     */
    public static final String URL_PROTOCOL_VFSFILE = "vfsfile";
    /**
     * URL 协议表示JBoss VFS资源: "vfs"
     */
    public static final String URL_PROTOCOL_VFS = "vfs";
    /**
     * Jar路径以及内部文件路径的分界符: "!/"
     */
    public static final String JAR_URL_SEPARATOR = "!/";
    /**
     * WAR路径及内部文件路径分界符: "*"
     */
    public static final String WAR_URL_SEPARATOR = "*/";

    // ------------------------------------------------------------------- get URL

    /**
     * 通过一个字符串形式的URL地址创建URL对象
     *
     * @param url URL
     * @return URL对象
     */
    public static URL url(String url) {
        return url(url, null);
    }

    /**
     * 通过一个字符串形式的URL地址创建URL对象
     *
     * @param url     URL
     * @param handler {@link URLStreamHandler}
     * @return URL对象
     */
    public static URL url(String url, URLStreamHandler handler) {
        if (ObjectUtil.isEmpty(url)) {
            return null;
        }

        // 兼容Spring的ClassPath路径
        if (url.startsWith(CLASSPATH_URL_PREFIX)) {
            url = url.substring(CLASSPATH_URL_PREFIX.length());
            return ClassLoaderUtil.getClassLoader().getResource(url);
        }

        try {
            return new URL(null, url, handler);
        } catch (MalformedURLException e) {
            // try again
            return null;
        }
    }

    /**
     * 获取URL中域名部分，只保留URL中的协议和域名部分
     * 例如: http://www.hutool.cn/aaa/bbb.htm 变为 http://www.hutool.cn
     *
     * @param url URL
     * @return 域名URL
     */
    public static String getHost(URL url) {
        if (null == url) {
            return null;
        }

        final int port = url.getPort();
        final StringBuilder sb = new StringBuilder();
        sb.append(url.getProtocol()).append("://").append(url.getHost());
        if (port > 0 && port != url.getDefaultPort()) {
            sb.append(":").append(port);
        }
        return sb.toString();
    }

    /**
     * 获取URL中的路径部分（不包括参数部分）
     * 例如: http://www.hutool.cn/aaa/bbb.htm?id=123 变为 /aaa/bbb.htm
     *
     * @param url URL
     * @return 路径
     */
    public static String getPath(URL url) {
        if (null == url) {
            return null;
        }
        return url.getPath();
    }

    /**
     * 获取URL中的参数部分
     * 例如: http://www.hutool.cn/aaa/bbb.htm?id=123 变为 id=123
     *
     * @param url URL
     * @return 参数部分
     */
    public static String getQuery(URL url) {
        if (null == url) {
            return null;
        }
        return url.getQuery();
    }

    /**
     * 获取URL中的锚点部分
     * 例如: http://www.hutool.cn/aaa/bbb.htm?id=123#test 变为 test
     *
     * @param url URL
     * @return 锚点部分
     */
    public static String getFragment(URL url) {
        if (null == url) {
            return null;
        }
        return url.getRef();
    }

    // ------------------------------------------------------------------- encode and decode

    /**
     * 编码URL，默认编码UTF-8
     *
     * @param url URL
     * @return 编码后的URL
     */
    public static String encode(String url) {
        return encode(url, StandardCharsets.UTF_8);
    }

    /**
     * 编码URL
     *
     * @param url     URL
     * @param charset 编码
     * @return 编码后的URL
     */
    public static String encode(String url, Charset charset) {
        if (ObjectUtil.isEmpty(url)) {
            return url;
        }
        return URLEncoder.encode(url, charset);
    }

    /**
     * 解码URL中的application/x-www-form-urlencoded字符串，默认编码UTF-8
     *
     * @param url URL
     * @return 解码后的URL
     */
    public static String decode(String url) {
        return decode(url, StandardCharsets.UTF_8);
    }

    /**
     * 解码URL中的application/x-www-form-urlencoded字符串
     *
     * @param url     URL
     * @param charset 编码
     * @return 解码后的URL
     */
    public static String decode(String url, Charset charset) {
        if (ObjectUtil.isEmpty(url)) {
            return url;
        }
        return URLDecoder.decode(url, charset);
    }

    // ------------------------------------------------------------------- build URL

    /**
     * 构建URL参数字符串
     *
     * @param params 参数
     * @return URL参数字符串
     */
    public static String buildQuery(Map<String, Object> params) {
        return buildQuery(params, StandardCharsets.UTF_8);
    }

    /**
     * 构建URL参数字符串
     *
     * @param params  参数
     * @param charset 编码
     * @return URL参数字符串
     */
    public static String buildQuery(Map<String, Object> params, Charset charset) {
        if (ObjectUtil.isEmpty(params)) {
            return "";
        }

        final StringBuilder sb = new StringBuilder();
        boolean isFirst = true;
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            if (isFirst) {
                isFirst = false;
            } else {
                sb.append("&");
            }
            sb.append(encode(entry.getKey(), charset)).append("=").append(encode(entry.getValue().toString(), charset));
        }
        return sb.toString();
    }

    /**
     * 构建完整的URL
     *
     * @param baseUrl 基础URL
     * @param params  参数
     * @return 完整URL
     */
    public static String buildURL(String baseUrl, Map<String, Object> params) {
        return buildURL(baseUrl, params, StandardCharsets.UTF_8);
    }

    /**
     * 构建完整的URL
     *
     * @param baseUrl 基础URL
     * @param params  参数
     * @param charset 编码
     * @return 完整URL
     */
    public static String buildURL(String baseUrl, Map<String, Object> params, Charset charset) {
        if (ObjectUtil.isEmpty(baseUrl)) {
            return baseUrl;
        }

        final String query = buildQuery(params, charset);
        if (ObjectUtil.isEmpty(query)) {
            return baseUrl;
        }

        return baseUrl + (baseUrl.contains("?") ? "&" : "?") + query;
    }

    // ------------------------------------------------------------------- file to URL

    /**
     * 将文件路径转换为URL
     *
     * @param file 文件
     * @return URL
     */
    public static URL fileToURL(File file) {
        if (null == file) {
            return null;
        }
        try {
            return file.toURI().toURL();
        } catch (MalformedURLException e) {
            return null;
        }
    }

    /**
     * 将文件路径转换为URL
     *
     * @param path 文件路径
     * @return URL
     */
    public static URL fileToURL(String path) {
        if (ObjectUtil.isEmpty(path)) {
            return null;
        }
        try {
            return new File(path).toURI().toURL();
        } catch (MalformedURLException e) {
            return null;
        }
    }

    // ------------------------------------------------------------------- URL to URI

    /**
     * 将URL转换为URI
     *
     * @param url URL
     * @return URI
     */
    public static URI urlToUri(URL url) {
        if (null == url) {
            return null;
        }
        try {
            return url.toURI();
        } catch (URISyntaxException e) {
            return null;
        }
    }

    /**
     * 将URI转换为URL
     *
     * @param uri URI
     * @return URL
     */
    public static URL uriToUrl(URI uri) {
        if (null == uri) {
            return null;
        }
        try {
            return uri.toURL();
        } catch (MalformedURLException e) {
            return null;
        }
    }

    // ------------------------------------------------------------------- normalize

    /**
     * 标准化URL字符串，包括：
     * <ol>
     *     <li>自动补齐“http://”头</li>
     *     <li>去除多余斜杠</li>
     *     <li>去除锚点</li>
     *     <li>处理相对路径问题</li>
     * </ol>
     *
     * @param url URL字符串
     * @return 标准化后的URL字符串
     */
    public static String normalize(String url) {
        return normalize(url, false);
    }

    /**
     * 标准化URL字符串，包括：
     * <ol>
     *     <li>自动补齐“http://”头</li>
     *     <li>去除多余斜杠</li>
     *     <li>根据参数决定是否去除锚点</li>
     *     <li>处理相对路径问题</li>
     * </ol>
     *
     * @param url              URL字符串
     * @param isRemoveFragment 是否去除锚点（#xxx部分）
     * @return 标准化后的URL字符串
     */
    public static String normalize(String url, boolean isRemoveFragment) {
        if (ObjectUtil.isEmpty(url)) {
            return url;
        }

        // 自动补齐协议头
        if (!url.startsWith("http://") && !url.startsWith("https://")) {
            url = "http://" + url;
        }

        // 去除锚点
        if (isRemoveFragment && url.contains("#")) {
            url = url.substring(0, url.indexOf("#"));
        }

        return url;
    }

    /**
     * 获取URL中的域名
     *
     * @param url URL
     * @return 域名
     */
    public static String getDomain(String url) {
        if (ObjectUtil.isEmpty(url)) {
            return null;
        }

        final URL u = url(url);
        if (null == u) {
            return null;
        }

        return u.getHost();
    }

    /**
     * 获取URL中的端口号
     *
     * @param url URL
     * @return 端口号，-1表示URL中未指定端口
     */
    public static int getPort(String url) {
        if (ObjectUtil.isEmpty(url)) {
            return -1;
        }

        final URL u = url(url);
        if (null == u) {
            return -1;
        }

        return u.getPort();
    }
}